package simpledb.common;

import simpledb.storage.DbFile;
import simpledb.storage.HeapFile;
import simpledb.storage.TupleDesc;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.*;

/**
 * Catalog 类用于追踪数据库中所有可用的表及其相关联的模式（schema）。
 * 当前这个类是一个存根目录，需要通过用户程序添加表后才能使用。最终目标是将它转换为一个可以从磁盘读取目录表的实现
 *
 * @Threadsafe 表示该类的方法可以安全地在多线程环境中调用
 */
public class Catalog {

    // 存储表信息的映射，键是表 ID，值是包含表信息的对象
    private final Map<Integer, TableInfo> tableInfoMap;

    // 存储表名到表 ID 的映射
    private final Map<String, Integer>    nameToIdMap;

    /**
     * 构造函数
     * 创建一个新的、空的目录
     */
    public Catalog() {
        this.tableInfoMap = new HashMap<>();
        this.nameToIdMap = new HashMap<>();
    }

    /**
     * 向目录中添加新表。
     * 此表的内容存储在指定的DbFile中。
     *
     * @param file 包含要添加表内容的文件；file.getId()是此文件/元组描述参数的标识符，用于getTupleDesc和getFile调用
     * @param name 表的名称 —— 可以为空字符串。不可以为null。如果存在名称冲突，则使用最后添加的表作为给定名称的表。
     * @param pkeyField 主键字段的名称
     */
    public void addTable(DbFile file, String name, String pkeyField) {
        final int tableId = file.getId();
        final TableInfo tableInfo = new TableInfo(tableId, name, file, pkeyField);
        this.tableInfoMap.put(tableId, tableInfo);
        this.nameToIdMap.put(name, tableId);
    }

    // 重载方法，不指定主键字段时默认为空字符串。
    public void addTable(DbFile file, String name) {
        addTable(file, name, "");
    }

    // 另一个重载方法，生成随机表名。
    public void addTable(DbFile file) {
        addTable(file, UUID.randomUUID().toString());
    }

    /**
     * 返回具有指定名称的表的ID，
     * 如果表不存在，则抛出NoSuchElementException异常。
     */
    public int getTableId(String name) throws NoSuchElementException {
        if (!this.nameToIdMap.containsKey(name)) {
            throw new NoSuchElementException();
        }
        return this.nameToIdMap.get(name);
    }

    // 获取表信息。
    private TableInfo getTableInfo(int tableId) throws NoSuchElementException {
        if (!this.tableInfoMap.containsKey(tableId)) {
            throw new NoSuchElementException();
        }
        return this.tableInfoMap.get(tableId);
    }

    /**
     * 返回指定表的元组描述（模式）。
     *
     * @param tableid 表的ID，如addTable中传递给DbFile.getId()函数所指定的那样。
     */
    public TupleDesc getTupleDesc(int tableid) throws NoSuchElementException {
        final DbFile dbFile = getTableInfo(tableid).getDbFile();
        if (dbFile != null) {
            return dbFile.getTupleDesc();
        }
        return null;
    }

    /**
     * 返回可用于读取指定表内容的DbFile。
     *
     * @param tableid 表的ID，如addTable中传递给DbFile.getId()函数所指定的那样。
     */
    public DbFile getDatabaseFile(int tableid) throws NoSuchElementException {
        return getTableInfo(tableid).getDbFile();
    }

    // 获取表的主键。
    public String getPrimaryKey(int tableid) throws NoSuchElementException {
        return getTableInfo(tableid).getPrimaryKeyName();
    }

    // 提供对表ID集合的迭代器。
    public Iterator<Integer> tableIdIterator() {
        final Set<Integer> ids = this.tableInfoMap.keySet();
        return ids.iterator();
    }

    // 根据表ID获取表名。
    public String getTableName(int tableid) {
        return getTableInfo(tableid).getTableName();
    }

    /** 清除所有表 */
    public void clear() {
        this.tableInfoMap.clear();
        this.nameToIdMap.clear();
    }

    /**
     * 从文件读取模式并创建数据库中的适当表。
     *
     * @param catalogFile 模式文件路径
     */
    public void loadSchema(String catalogFile) {
        String line = "";
        String baseFolder = new File(new File(catalogFile).getAbsolutePath()).getParent();
        try {
            BufferedReader br = new BufferedReader(new FileReader(new File(catalogFile)));
            while ((line = br.readLine()) != null) {
                // 假设行格式为 name (field type, field type, ...)
                String name = line.substring(0, line.indexOf("(")).trim();
                String fields = line.substring(line.indexOf("(") + 1, line.indexOf(")")).trim();
                String[] els = fields.split(",");
                ArrayList<String> names = new ArrayList<>();
                ArrayList<Type> types = new ArrayList<>();
                String primaryKey = "";
                for (String e : els) {
                    String[] els2 = e.trim().split(" ");
                    names.add(els2[0].trim());
                    if (els2[1].trim().toLowerCase().equals("int"))
                        types.add(Type.INT_TYPE);
                    else if (els2[1].trim().toLowerCase().equals("string"))
                        types.add(Type.STRING_TYPE);
                    else {
                        System.out.println("未知类型 " + els2[1]);
                        System.exit(0);
                    }
                    if (els2.length == 3) {
                        if (els2[2].trim().equals("pk"))
                            primaryKey = els2[0].trim();
                        else {
                            System.out.println("未知注解 " + els2[2]);
                            System.exit(0);
                        }
                    }
                }
                Type[] typeAr = types.toArray(new Type[0]);
                String[] namesAr = names.toArray(new String[0]);
                TupleDesc t = new TupleDesc(typeAr, namesAr);
                HeapFile tabHf = new HeapFile(new File(baseFolder + "/" + name + ".dat"), t);
                addTable(tabHf, name, primaryKey);
                System.out.println("已添加表: " + name + " 和模式 " + t);
            }
        } catch (IOException | IndexOutOfBoundsException e) {
            e.printStackTrace();
            System.exit(0);
        }
    }
}
